SpringCloud Hystrix 配置线程池
信号量、线程池
参考资料 隔离技术线程池(ThreadPool)和信号量(semaphore)
Hystrix 内部提供了两种模式执行逻辑:信号量、线程池。默认情况 Hystrix 采用的是线程池
可以通过 Thread.currentThread().getName()
查看当前的线程
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 是否是异步调用 | 资源消耗 |
---|---|---|---|---|---|
线程池隔离 | 支持,可直接返回 | 支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断 | 每个服务单独用线程池 | 可以是异步,也可以是同步。看调用的方法 | 大,大量线程的上下文切换,容易造成机器负载高 |
信号量隔离 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) | 支持,当信号量达到 maxConcurrentRequests 后。再请求会触发 fallback | 通过信号量的计数器 | 同步调用,不支持异步 | 小,只是个计数器 |
信号量模式(SEMAPHORE)
在该模式下,接收请求和执行下游依赖在同一个线程内完成,不存在线程上下文切换所带来的性能开销,如果调用多个下游服务时也是同一个线程,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)
另外,为了限制对下游依赖的并发调用量,可以配置 Hystrix 的 execution.isolation.semaphore.maxConcurrentRequests
,当并发请求数达到阈值时,请求线程可以快速失败,执行降级
实现也很简单,一个简单的计数器,当请求进入熔断器时,执行 tryAcquire()
,计数器加 1,结果大于阈值的话,就返回 false,发生信号量拒绝事件,执行降级逻辑。当请求离开熔断器时,执行 release()
,计数器减 1。
线程池模式
接收用户请求采用 Tomcat 的线程池,而到了执行业务代码、调用其他服务时,则采用 Hystrix 的线程池(Hystrix 会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。这样某个服务挂了也只是这个服务的线程池莫得了,其他的线程池还是能正常工作)
在该模式下,用户请求会被提交到 各自的线程池中执行,把执行每个下游服务的线程分离,从而达到资源隔离的作用。当线程池来不及处理并且请求队列塞满时,新进来的请求将快速失败,可以避免依赖问题扩散。
在信号量模式提到的问题,对所依赖的多个下游服务,通过线程池的异步执行,可以有效的提高接口性能。
优势
减少所依赖服务发生故障时的影响面,比如 ServiceA 服务发生异常,导致请求大量超时,对应的线程池被打满,这时并不影响ServiceB、ServiceC 的调用。 如果接口性能有变动,可以方便的动态调整线程池的参数或者是超时时间(前提是 Hystrix 参数实现了动态调整)
缺点
请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的开销。 因为涉及到跨线程,那么就存在 ThreadLocal 数据的传递问题,比如在主线程初始化的 ThreadLocal 变量,在线程池线程中无法获取
下面的配置项(命令模式)默认在 HystrixCommandProperties
类里
可以自己设置隔离策略
设置项:THREAD
、SEMAPHORE
(信号量)
// name = execution.isolation.strategy
// to use thread isolation
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
// to use semaphore isolation
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
线程池的配置
参考资料 Hystrix 线程隔离技术解析-线程池 配置文档
如果使用 Tomcat 的线程池去接收用户的请求(Tomcat 是一个线程一个请求,即大力出奇迹),如果某一个服务出现了故障,这个时候后续有大量的请求过来,那么容器中的线程数量则会持续增加直致 CPU 资源耗尽到 100% (雪崩),导致 Tomcat 无法处理其他业务功能
Hystrix 线程池的配置(常用的) 更多的命令可以看文档
1、执行超时时间(只针对线程池) Default Value:1000
// name = execution.isolation.thread.timeoutInMilliseconds
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(int value)
2、启动超时时间配置(默认开启)
// name = execution.timeout.enabled
HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(boolean value)
3、超时时是否中断线程(默认 true)
// name = execution.isolation.thread.interruptOnTimeout
HystrixCommandProperties.Setter()
.withExecutionIsolationThreadInterruptOnTimeout(boolean value)
4、取消之后是否中断线程(默认 false)
// name = execution.isolation.thread.interruptOnCancel
HystrixCommandProperties.Setter()
.withExecutionIsolationThreadInterruptOnCancel(boolean value)
如何配置
@GetMapping("/search/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack" ,commandProperties = {
// 设置隔离策略
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 设置超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
})
public Customer findById(@PathVariable Integer id) {
return searchClient.findById(id);
}
// findById 的降级方法,方法描述要和接口一致
public Customer findByIdFallBack(@PathVariable Integer id) {
return new Customer(-1, "", 0);
}
信号量的配置
最大并发请求(默认 10)
// name = execution.isolation.semaphore.maxConcurrentRequests
HystrixCommandProperties.Setter()
.withExecutionIsolationSemaphoreMaxConcurrentRequests(int value)